/*****************************************************************************
 *
 * Weber State University
 * CS4280 Spring 2010
 * Final Group Assignment
 *
 *
 *   TTTTTT                               CCCCC
 *     TT   EEEEE   A    M   M          CC      C
 *     TT   E      A A   M   M         CC   
 *     TT   E     A   A  MM MM  -----  CC
 *     TT   EEE   AAAAA  M M M  -----  CC
 *     TT   E     A   A  M   M          CC      C
 *     TT   EEEEE A   A  M   M            CCCCC
 *
 *   Jared Pack ---- Jon Mann ---- Joshua Clayton
 *
 *
 * 
 *
 ****************************************************************************/

 /* FILENAME OGLWindow.cpp
  *
  * Change control Section
  *
  * Team Member: Jon Mann
  * Version: 0.1
  * Date: 04/6/2010
  *     Added code to Init() to set up our lighting
  *      
  * Team Member: Jon Mann
  * Version: 0.2
  * Date: 04/7/2010
  *     Added keyboard functions for jumping (kindof), shooting, and a forward sprint
  *        Rebuilt the mouse movement code in OnMouseMove() to enable infinite yaw
  *        and all around better control
  *
  * Team Member: Jon Mann
  * Version: 0.3
  * Date: 04/9/2010
  *     Turned off cursor in Init();
  *        Minor changes to jumping input
  *
  * Team Member: Jon Mann
  * Version: 0.4
  * Date: 04/10/2010
  *     Added InvertYControl() to enable control style change
  *        Added ToggleCursor() to enable switching cursor visibility
  *        Added keyboard input and set mouse control to work 
  *            with control style
  *        Added ToggleNearGod() to enable "near-God mode"--it's pretty
  *            limited, so "God mode" seemed like an exaggeration...
  *        Changed the code in Render() so that we only render the world
  *            when we're not in "near God mode"
  *
  * Team Member: Jon Mann
  * Version: 0.5
  * Date: 04/11/2010
  *     Added OnMouseDownR() to handle jumping
  *
  * Team Member: Jon Mann
  * Version: 0.6
  * Date: 04/14/2010
  *		Rewrote pop-up message to reflect new controls
  *		Changed OnMouseMove() to work with SetCursorPos() call
  *      
  */

#ifdef __linux__
#include <stdio.h>
#include <stdlib.h>
#include <GL/glx.h>
#include <X11/extensions/xf86vmode.h>
#include <X11/keysym.h>
#endif

#ifdef _WINDOWS
#include <windows.h>
extern LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
extern HINSTANCE hInstance;
#endif

#include <GL/gl.h>
#include <GL/glu.h>
#include <math.h>
#include "OGLWindow.h"

// disable implicit float-double casting
#pragma warning(disable:4305)

OGLWindow::OGLWindow()
{
}

OGLWindow::~OGLWindow()
{
}

// switch controls style
void OGLWindow::InvertYControl()
{
    yControl = -yControl;
}

// switch cursor visibility
void OGLWindow::ToggleCursor(void)
{
    isCursorVis = !isCursorVis;
#ifdef _WIN32
// Gah! can't find an easy way to do this in linux.  best hope so far:
// system("unclutter");... but I don't want to do that.
    ShowCursor(isCursorVis);
#endif        
}

// toggle "near-God mode"
void OGLWindow::ToggleNearGod()
{
    nearGod = !nearGod;
}

bool OGLWindow::Init()
{   
	xAdj = 0.98f;
    yAdj = 0.9f;
    timer = new  CHiResTimer();	
#ifdef _WIN32
    ShowCursor(false); // turn off cursor
#endif    
    isCursorVis = false; // track cursor state
    yControl = 1; // airplane controls
    // clear to black background
    glClearColor(0.0, 0.0, 0.0, 0.0);
    // Begin - Phase 6
    mouseSensitivity = 2.0f;
    // End - Phase 6
    m_angle = 0.0f;
    // Begin - Phase 5
    gameCamera = new CCamera;
    // End - Phase 5
    // Begin - Phase 11
    gameWorld = new CWorld(gameCamera);
    // End - Phase 11

    glEnable(GL_LIGHTING); // enable lighting
    glShadeModel(GL_SMOOTH);
    glEnable(GL_DEPTH_TEST);  // for removal of hidden surfaces
    glEnable(GL_NORMALIZE);  // normalize vectors for proper shading

    return true;
}

bool OGLWindow::Shutdown()
{
    // Begin - Phase 11
    // Begin - Phase 5
    delete gameCamera;
    gameWorld = NULL;
    gameCamera = NULL;
	delete gameWorld;
    // End - Phase 5
    // End - Phase 11
#ifdef __linux__
    if (ctx)
    {
        if (!glXMakeCurrent(dpy, None, NULL))
        {
            fprintf(stderr, "Could not release drawing context.\n");
        }
        glXDestroyContext(dpy, ctx);
        ctx = NULL;
    }
    if (fullscreen)
    {
        XF86VidModeSwitchToMode(dpy, screen, &deskMode);
        XF86VidModeSetViewPort(dpy, screen, 0, 0);
    }
    XCloseDisplay(dpy);
#endif        

#ifdef _WIN32
    if (fullscreen)
    {
        ChangeDisplaySettings(NULL, 0);
        ShowCursor(TRUE);
    }
#endif        

    return true;
}

void OGLWindow::SetupProjection(int w, int h)
{
    if (height == 0)                    // don't want a divide by zero
    {
        height = 1;                 
    }

    glViewport(0, 0, width, height);        // reset the viewport to new dimensions
    glMatrixMode(GL_PROJECTION);            // set projection matrix current matrix
    glLoadIdentity();                       // reset projection matrix

    // calculate aspect ratio of window
    gluPerspective(52.0f,(GLfloat)width/(GLfloat)height,1.0f,1000.0f);

    glMatrixMode(GL_MODELVIEW);             // set modelview matrix
    glLoadIdentity();                       // reset modelview matrix

    width = w;
    height = h;
}
// Begin - Phase 17 Remove parameter
void OGLWindow::Prepare()
// End - Phase 17
{
    // Phase 11 - Move following two statements from Render
    // clear screen and depth buffer
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);     
//    glMatrixMode(GL_MODELVIEW); 
    glLoadIdentity();
    m_angle += 0.1f;
}

void OGLWindow::OnMouseMove(int x, int y)
{
	
    int mouseX = x;
    int mouseY = y;
    if (fullscreen == false)
        {
            // pass the direction and speed the mouse moved
            mouseX = x - (width/2);//*xAdj;
			mouseY = y - (height/2);//*yAdj;
        }
        else
        {
            // pass the direction and speed the mouse moved
            mouseX = x - width/2;
			mouseY = y - height/2;
        }
	
	if (!gameWorld->IsGameDone())
    {
        gameCamera->yaw += mouseX * mouseSensitivity/10;
        gameCamera->pitch += mouseY * mouseSensitivity/10 * yControl;
    }
	
	/*
    if (!gameWorld->IsGameDone())
    {
        gameCamera->yaw += (x - width / 2) * mouseSensitivity/10;
        gameCamera->pitch += (y - height / 2) * mouseSensitivity/10 * yControl;
    }*/
	if (!isCursorVis)
    {
        // reset cursor to center of screen
#ifdef _WIN32
        //SetCursorPos((width / 2) * xAdj, (height / 2) * yAdj);
		SetCursorPos((width / 2), (height / 2));
#endif
    }
}

// End - Phase 6

// Begin - Phase 9

void OGLWindow::OnKeyDown(int nVirtKey)
{
    // Phase 18 - Add gameDone logic
    if (!gameWorld->IsGameDone())
    {    
        switch (nVirtKey)
        {    
            //Begin - Phase 10
//		case TOGGLE_FULLSCREEN:
#ifdef __linux
			killGLWindow();
            fullscreen = !fullscreen;
            createGLWindow("NeHe's Textures, Lighting & Keyboard Tutorial",           
            640, 480, 24, fullscreen);
#endif
        case GO_UP:
            gameCamera->velocity += CVector(0,0,5.0);
            break;
        case GO_UP_FAST:
            gameCamera->velocity += CVector(0,0,10.0);
            break;
        case GO_DOWN:
            gameCamera->velocity += CVector(0,0,-5.0);
            break;
        case GO_RIGHT:
            gameCamera->velocity += CVector(5.0, 0.0, 0.0);
            break;
        case GO_LEFT:
            gameCamera->velocity += CVector(-5.0, 0.0, 0.0);
            break;
        case INVERT_Y:
            InvertYControl();
            break;
        case NEAR_GOD_MODE:
            ToggleNearGod();
            break;
        case CURSOR:
            ToggleCursor();
            break;
        case GO_JUMP:
            gameCamera->Jump();
            break;
        case GO_SHOOT:
            gameWorld->player->FireWeapon();
            break;
        case MOUSE_SENSE_UP:
            mouseSensitivity += 0.05f;
            break;
		case MOUSE_CENTER_LEFT:
            xAdj += .003f;
            break;
		case MOUSE_CENTER_RIGHT:
            xAdj -= .003f;
            break;
		case MOUSE_CENTER_UP:
            yAdj += .004f;
            break;
		case MOUSE_CENTER_DOWN:
			yAdj -= .004f;
            break;
        case MOUSE_SENSE_DOWN:
            mouseSensitivity -= 0.05f;
            if (mouseSensitivity < 0.05)
                mouseSensitivity = 0.05;
            break;
        default:
            break;
        }
    }
}
// End - Phase 9
// Begin - Phase 16
void OGLWindow::OnMouseDownL(int x, int y) 
{
    if (!gameWorld->IsGameDone())
        gameWorld->player->FireWeapon();
//    else
//        OnMouseDownL(0,0);
}

void OGLWindow::OnMouseDownR(int x, int y) 
{
    if (!gameWorld->IsGameDone())
        gameCamera->Jump();
}

// End - Phase 16
// Begin - Phase 17 Add float parameter
void OGLWindow::Render(void)
// End - Phase 17
{
    float deltaTime = timer->GetElapsedSeconds();	
    CCamera *camera = OnGetCamera();
    // Begin - Phase 11
    CWorld *world = OnGetWorld();

    world->Prepare();
    camera->Animate(deltaTime);

    // only update certain things
    if (nearGod)
    {
        
        // End - Phase 11
        
        // Begin - Phase 5
        // For phase 10, change to 0.05
        // Phase 17 - change parameter to deltaTime
        
        // End - Phase 5

        // Begin - Phase 11
        // Phase 17 - change parameter to deltaTime
        world->Animate(deltaTime);
    }
    world->Draw(camera);

#ifdef _WIN32
   SwapBuffers(hDC);
#endif        
}

/* this function creates our window and sets it up properly */
/* FIXME: bits is currently unused */
bool OGLWindow::createGLWindow(char* title, int w, int h, bool fs)
{
#ifdef __linux__
    XVisualInfo *vi;
    Colormap cmap;
    int dpyWidth, dpyHeight;
    int i;
    int glxMajorVersion, glxMinorVersion;
    int vidModeMajorVersion, vidModeMinorVersion;
    XF86VidModeModeInfo **modes;
    int modeNum;
    int bestMode;
    Atom wmDelete;
    Window winDummy;
    unsigned int borderDummy;
#endif        
    width = w;
    height = h;
#ifdef _WIN32
// initialize RECT windowRect
    RECT windowRect;
    windowRect.left=0L;
    windowRect.right=(long)width;
    windowRect.top=0L;
    windowRect.bottom=(long)height;

    WNDCLASSEX wc; // Window class (describes the windows attributes)
    wc.cbSize            = sizeof(WNDCLASSEX);
    wc.cbClsExtra        = 0;
    wc.cbWndExtra        = 0;
    wc.hbrBackground    = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.hCursor            = LoadCursor (NULL, IDC_ARROW);
    wc.hIcon            = LoadIcon (NULL, IDI_APPLICATION);
    wc.hIconSm            = LoadIcon (NULL, IDI_WINLOGO);
    wc.hInstance        = hInstance;
    wc.lpfnWndProc        = WndProc;
    wc.lpszClassName    = "ShooterGame";
    wc.lpszMenuName        = NULL;
    wc.style            = CS_HREDRAW | CS_VREDRAW;
    if (!RegisterClassEx(&wc))
    {
        MessageBox (NULL,"Error: Cannot Register Class","ERROR!",MB_OK);
        return (0);
    }
#endif        
    
    fullscreen = fs;
#ifdef __linux__
    /* set best mode to current */
    bestMode = 0;
    /* get a connection */
    dpy = XOpenDisplay(0);
    screen = DefaultScreen(dpy);
    XF86VidModeQueryVersion(dpy, &vidModeMajorVersion,
        &vidModeMinorVersion);
    printf("XF86VidModeExtension-Version %d.%d\n", vidModeMajorVersion,
        vidModeMinorVersion);
    XF86VidModeGetAllModeLines(dpy, screen, &modeNum, &modes);
    /* save desktop-resolution before switching modes */
    deskMode = *modes[0];
    /* look for mode with requested resolution */
    for (i = 0; i < modeNum; i++)
    {
        if ((modes[i]->hdisplay == width) && (modes[i]->vdisplay == height))
        {
            bestMode = i;
        }
    }
    /* get an appropriate visual */
    vi = glXChooseVisual(dpy, screen, attrListDbl);
    if (vi == NULL)
    {
        vi = glXChooseVisual(dpy, screen, attrListSgl);
        printf("Only Singlebuffered Visual!\n");
    }
    else
    {
        printf("Got Doublebuffered Visual!\n");
    }
    glXQueryVersion(dpy, &glxMajorVersion, &glxMinorVersion);
    printf("glX-Version %d.%d\n", glxMajorVersion, glxMinorVersion);
    /* create a GLX context */
    ctx = glXCreateContext(dpy, vi, 0, GL_TRUE);
    /* create a color map */
    cmap = XCreateColormap(dpy, RootWindow(dpy, vi->screen),
        vi->visual, AllocNone);
    attr.colormap = cmap;
    attr.border_pixel = 0;
#endif        

    if (fullscreen)
    {
#ifdef __linux__
        XF86VidModeSwitchToMode(dpy, screen, modes[bestMode]);
        XF86VidModeSetViewPort(dpy, screen, 0, 0);
        dpyWidth = modes[bestMode]->hdisplay;
        dpyHeight = modes[bestMode]->vdisplay;
        printf("Resolution %dx%d\n", dpyWidth, dpyHeight);
        XFree(modes);
    
        /* create a fullscreen window */
        attr.override_redirect = True;
        attr.event_mask = ExposureMask | KeyPressMask | ButtonPressMask |
            StructureNotifyMask;
        win = XCreateWindow(dpy, RootWindow(dpy, vi->screen),
            0, 0, dpyWidth, dpyHeight, 0, vi->depth, InputOutput, vi->visual,
            CWBorderPixel | CWColormap | CWEventMask | CWOverrideRedirect,
            &attr);
        XWarpPointer(dpy, None, win, 0, 0, 0, 0, 0, 0);
		XMapRaised(dpy, win);
        XGrabKeyboard(dpy, win, True, GrabModeAsync,
            GrabModeAsync, CurrentTime);
        XGrabPointer(dpy, win, True, ButtonPressMask,
            GrabModeAsync, GrabModeAsync, win, None, CurrentTime);
#endif        
#ifdef _WIN32
        DEVMODE dmScreenSettings;                   // device mode
        memset(&dmScreenSettings,0,sizeof(dmScreenSettings));
        dmScreenSettings.dmSize = sizeof(dmScreenSettings);
        dmScreenSettings.dmPelsWidth = w;         // screen width
        dmScreenSettings.dmPelsHeight = h;           // screen he    ight
        dmScreenSettings.dmBitsPerPel = 24;             // bits per     pixel
        dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
        
        if (ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
        {
            // setting display mode failed, switch to windowed
            MessageBox(NULL, "Display mode failed", NULL, MB_OK);
            fullscreen = FALSE;
            dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; //Extended Style 
            dwStyle=WS_OVERLAPPEDWINDOW; // Windows Style
        }
#endif        
    }
    else
    {
#ifdef __linux__
        /* create a window in window mode*/
        attr.event_mask = ExposureMask | KeyPressMask | ButtonPressMask |
            StructureNotifyMask;
        win = XCreateWindow(dpy, RootWindow(dpy, vi->screen),
            0, 0, width, height, 0, vi->depth, InputOutput, vi->visual,
            CWBorderPixel | CWColormap | CWEventMask, &attr);
        /* only set window title and handle wm_delete_events if in windowed mode */
        wmDelete = XInternAtom(dpy, "WM_DELETE_WINDOW", True);
        XSetWMProtocols(dpy, win, &wmDelete, 1);
        XSetStandardProperties(dpy, win, title,
            title, None, NULL, 0, NULL);
        XMapRaised(dpy, win);
#endif        
#ifdef _WIN32
        dwExStyle=WS_EX_APPWINDOW; // Window Extended Style
        dwStyle=WS_POPUP;          // Windows Style
        ShowCursor(FALSE);         // Hide Mouse Pointer
#endif        
    }       
#ifdef _WIN32
    hwnd = CreateWindowEx(NULL, "ShooterGame", "Shooting Game Application",
                          dwStyle, 0, 0, windowRect.right - windowRect.left,
                          windowRect.bottom - windowRect.top,
                          NULL, NULL, hInstance, NULL);
    if (!hwnd)              
    {                       
        MessageBox (NULL,"Error: Failed to Create Window","ERROR!",MB_OK);
        return (0);
    }   
    hDC = GetDC(hwnd);
    ShowWindow(hwnd, SW_SHOW);
    UpdateWindow(hwnd);
#endif        

#ifdef __linux__
    /* connect the glx-context to the window */
    glXMakeCurrent(dpy, win, ctx);
    XGetGeometry(dpy, win, &winDummy, &x, &y, &width,
                 &height, &borderDummy, &depth);
    printf("Depth %d\n", depth);
    if (glXIsDirect(dpy, ctx)) 
        printf("Congrats, you have Direct Rendering!\n");
    else
        printf("Sorry, no Direct Rendering possible!\n");
#endif
    return true;    
}

#ifdef __linux__
int OGLWindow::Xevent_loop(void)
{
    XEvent event;
    KeySym key;
    
    /* handle the events in the queue */
    while (XPending(dpy) > 0)
    {
        XNextEvent(dpy, &event);
        switch (event.type)
        {
            case Expose:
	        if (event.xexpose.count != 0)
	            break;
                Render();
         	    break;
            case ConfigureNotify:
                /* call resizeGLScene only if our window-size changed */
                if ((event.xconfigure.width != width) || 
                    (event.xconfigure.height != height))
                {
                    width = event.xconfigure.width;
                    height = event.xconfigure.height;
                    SetupProjection(width, height);
                }
                break;
            /* exit in case of a mouse button press */
            case ButtonPress:     
                return BUTTON_QUIT;
                break;
            case KeyPress:
                key = XLookupKeysym(&event.xkey, 0);
                OnKeyDown(GLuint key);
                break;
            case ClientMessage:    
                if (*XGetAtomName(dpy, event.xclient.message_type)
                    == *"WM_PROTOCOLS")
                {
                    printf("Exiting sanely...\n");
                    done = True;
                }
                break;
            default:
                break;
        }
    }
    return 0;
}
#endif